import Foundation
import EventKit

public typealias CSEvent = EKEvent
public typealias CSEventStore = EKEventStore
public typealias CSEventAlarm = EKAlarm
public typealias CSWeekday = EKWeekday
public typealias CSReminder = EKReminder

public struct CalendarShared {
    public static let shared = CalendarShared()
    
    let solarFestive: SolarFestive
    let lunarFestive: LunarFestive
    
    private var store = EKEventStore()
    
    public init() {
        guard let lunar = Bundle.module.url(forResource: "lunar", withExtension: "json"),
              let solar = Bundle.module.url(forResource: "solar", withExtension: "json"),
              let lunarData = try? Data(contentsOf: lunar),
              let solarData = try? Data(contentsOf: solar),
              let solarEntity = try? JSONDecoder().decode(SolarFestive.self, from: solarData),
              let lunarEntity = try? JSONDecoder().decode(LunarFestive.self, from: lunarData)
        else {
            solarFestive = SolarFestive()
            lunarFestive = LunarFestive()
            return
        }
        solarFestive = solarEntity
        lunarFestive = lunarEntity
    }
    
    public func getMonth(_ date: Date, firstWeek: Week = .sun) -> MonthModel {
        let month = date.month
        let year = date.year
        var calendar = Calendar.chinese
        calendar.firstWeekday = firstWeek.value
        let days = generateDates(for: date, calendar: calendar)
        // 获取日历事件和提醒事项
        fetchEventAndReminder(days)
        let monthModel = MonthModel(days: days, month: month, year: year)
        return monthModel
    }

    public func getMonth(_ date: Date, firstWeek: Week = .sun, completion: @escaping (MonthModel) -> Void) {
        DispatchQueue(label: "getMonth").async {
            let month = getMonth(date, firstWeek: firstWeek)
            DispatchQueue.main.async {
                completion(month)
            }
        }
    }

    public func getWeek(_ date: Date, firstWeek: Week = .sun) -> [DayModel] {
        var calendar = Calendar.chinese
        calendar.firstWeekday = firstWeek.value
        let entrys: [DayModel] = calendar.generateWeekDates(for: date).compactMap { val in
            var mode: DayModel.Mode = .current
            if date.isLastMonth(with: val) {
                mode = .last
            } else if date.isNextMonth(with: val) {
                mode = .next
            }
            return getDay(val, mode: mode)
        }
        // 获取日历事件和提醒事项
        fetchEventAndReminder(entrys)
        return entrys
    }
    
    public func getDay(_ date: Date, mode: DayModel.Mode = .current) -> DayModel {
        let key = String(format: "%02d%02d", date.month, date.day)
        let wKey = String(format: "%02d%d%d", date.month, date.weekdayOrdinal, date.weekday + 1)
        let lKey = String(format: "%02d%02d", date.lunarMonth, date.lunarDay)
        
        let lunar_festival = lunarFestive.festival
        let solarTerms = lunarFestive.solarTerms
        let chuxi = lunarFestive.chuxi
        let festiveEvents = solarFestive.festiveEvents
        let solar_festive = solarFestive.festive
        let weekFestive = solarFestive.weekFestive
        let weekFestiveEvents = solarFestive.weekFestiveEvents
        let rest = solarFestive.rest
        
        var lunarDay = date.lunarDayString
        var festive: [String] = []
        if let value = chuxi["\(date.year)"], key == value {
            festive.append("除夕")
            lunarDay = "除夕"
        }
        if let value = lunar_festival[lKey] {
            festive.append(value)
        }
        if let index = solarTerms["\(date.year)"]?.firstIndex(of: key) {
            festive.append(Lunar.terms[index])
            lunarDay = Lunar.terms[index]
        }
        if let event = festiveEvents[key] {
            festive.append(contentsOf: event)
        }
        if let event = weekFestiveEvents[wKey] {
            festive.append(contentsOf: event)
        }
        
        var tiaoxiu: DayModel.TiaoXiu = .none
        if let value = rest["\(date.year)"]?[key] {
            tiaoxiu = DayModel.TiaoXiu(rawValue: value) ?? .none
        }
        
        if let value = lunar_festival[lKey] {
            if Preference.shared.showFestival {
                lunarDay = value
            }
        } else if let value = weekFestive[wKey] {
            lunarDay = value
        } else if let value = solar_festive[key] {
            lunarDay = value
        } else {
            if !Preference.shared.showLunar {
                lunarDay = ""
            }
        }
        return DayModel(festivals: festive, lunar: lunarDay, date: date, mode: mode, tiaoxiu: tiaoxiu)
    }
    
    // 获取日历事件和提醒事项
    func fetchEventAndReminder(_ days: [DayModel]) {
        // 获取日历事件
        if Preference.shared.showEvent, let first = days.first?.date, let last = days.last?.date {
            let events = fetchEvents(with: first, end: last)
            days.forEach { entity in
                entity.events = events.filter({ $0.startDate.isSameDay(as: entity.date) })
            }
        }
        // 获取提醒事项
        if Preference.shared.showReminder {
            fetchReminders { values in
                days.forEach { model in
                    model.reminders = values.filter({ $0.creationDate?.isSameDay(as: model.date) ?? false })
                }
            }
        }
    }
    
    public func fetchEvents(with start: Date, end: Date) -> [CSEvent] {
        guard let yesterday = start.yesterday, let tomorrow = end.tomorrow else { return [] }
        let calendars = store.calendars(for: .event).filter {
            "中国大陆节假日" != $0.title
        }
        let predicate = store.predicateForEvents(withStart: yesterday, end: tomorrow, calendars: calendars)
        return store.events(matching: predicate)
    }
    
    public func fetchReminders(_ callback: @escaping ([CSReminder]) -> Void) {
        let calendars = store.calendars(for: .reminder)
        let predicate = store.predicateForReminders(in: calendars)
        store.fetchReminders(matching: predicate) { reminders in
            callback(reminders ?? [])
        }
    }

//    func generateDays(for date: Date, calendar: Calendar) -> [DayModel] {
//        let currentMonth = calendar.dateInterval(of: .month, for: date)!
//        let firstDayOfMonth = currentMonth.start
//        let firstWeekday = calendar.component(.weekday, from: firstDayOfMonth) - 1
//        let daysInMonth = calendar.range(of: .day, in: .month, for: date)!.count
//
//        let startOffset = firstWeekday
//        let startDate = calendar.date(byAdding: .day, value: -startOffset, to: firstDayOfMonth)!
//
//        return (0..<42).map { offset in
//            let val = calendar.date(byAdding: .day, value: offset, to: startDate)!
//            var mode: DayModel.Mode = .current
//            if date.isLastMonth(with: val) {
//                mode = .last
//            } else if date.isNextMonth(with: val) {
//                mode = .next
//            }
//            return getDay(val, mode: mode)
//        }
//    }

    func generateDates(for date: Date, calendar: Calendar) -> [DayModel] {
        var dates: [DayModel] = []
        guard let range = calendar.range(of: .day, in: .month, for: date) else {
            return dates
        }
        // Range(1..<?) to 0..<?
        let startOfMonth = date.startOfMonth
        for value in Array(range).indices {
            let date = calendar.date(byAdding: .day, value: value, to: startOfMonth) ?? startOfMonth
            dates.append(getDay(date, mode: .current))
        }

        var weekday = startOfMonth.weekday
        weekday += weekday >= calendar.firstWeekday ? 0 : 7

        let lastCount = weekday - calendar.firstWeekday

        if lastCount > 0 {
            var lastDates: [DayModel] = []
            for value in (1...lastCount).reversed() {
                let dayDate = calendar.date(byAdding: .day, value: Int(-value), to: startOfMonth) ?? startOfMonth
                let mode: DayModel.Mode = date.isLastMonth(with: dayDate) ? .last : .current
                lastDates.append(getDay(dayDate, mode: mode))
            }
            dates = lastDates + dates
        }

        let nextCount = 42 - dates.count

        if nextCount > 0, let endOfMonth = dates.last?.date {
            var nextDates: [DayModel] = []
            for value in 1...nextCount {
                let dayDate = calendar.date(byAdding: .day, value: value, to: endOfMonth) ?? endOfMonth
                let mode: DayModel.Mode = date.isNextMonth(with: dayDate) ? .next : .current
                nextDates.append(getDay(dayDate, mode: mode))
            }
            dates += nextDates
        }
        return dates
    }
}
